﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading.Tasks;

namespace FeatureSelection
{
    class FeatureSelection
    {
        double TrainingLength;
        double TestLength;
        int K = 3; // determines number of nearest neighbors for k_NN methods
        int columnY; // number of column Y in data, this should be done in ExampleSet class
        public int NumberOfNearestNeigbors { get { return K; } set { K = value; } }
        public int ColumnYNumber { get { return columnY; } set { columnY = value; } }


        private double EuclideanDistance(double[,] SortedData, int CenterNumber, int VectorNumber)
        {
            double temp = 0;
            for (int i = 0; i < SortedData.GetLength(1); i++)
            {
                temp += Math.Pow(SortedData[CenterNumber, i] - SortedData[VectorNumber, i], 2);
            }
            return Math.Sqrt(temp);
        }
        private double EuclideanDistance(double[,] SortedData, double DataCenterNumber, double DataVectorNumber, int CenterNumber, int VectorNumber)
        {
            double temp = 0;
            for (int i = 0; i < SortedData.GetLength(1); i++)
            {
                temp = temp + Math.Pow(SortedData[CenterNumber, i] - SortedData[VectorNumber, i], 2);
            }
            temp += Math.Pow(DataCenterNumber - DataVectorNumber, 2);
            return Math.Sqrt(temp);
        }

        public double k_NN(double[,] SortedData, double[] Y, int VectorNumber) // used for one double[,]
        {
            double prediction = 0; // predicted value of Y[VectorNumber]
            double[,] NearestNeighbors = new double[K, 2]; // array contains numbers of nearest neigbors of SortedData[VectorNumber,]
            // filling NearestNeigbors with  first k vectors from SortedData
            for (int i = 0; i < K; i++)
            {
                NearestNeighbors[i, 0] = (double)i;
                NearestNeighbors[i, 1] = EuclideanDistance(SortedData, VectorNumber, i);
            }
            // Sorting NearestNeighbors ([0] - nearest, [k] - farest)
            double p;
            double Distance;
            for (int i = 0; i < K - 1; i++)
            {
                for (int j = i; j < K - 1; j++)
                {
                    if (NearestNeighbors[j, 1] > NearestNeighbors[j + 1, 1])
                    {
                        p = NearestNeighbors[j, 0];
                        Distance = NearestNeighbors[j, 1];
                        NearestNeighbors[j, 0] = NearestNeighbors[j + 1, 0];
                        NearestNeighbors[j, 1] = NearestNeighbors[j + 1, 1];
                        NearestNeighbors[j + 1, 0] = p;
                        NearestNeighbors[j + 1, 0] = Distance;
                    }
                }

            }
            // Finding nearest neighbors of SortedData[VectorNumber,]
            for (int i = K; i < TrainingLength; i++)
            {
                Distance = EuclideanDistance(SortedData, VectorNumber, i);
                for (int j = 0; j < K; j++)
                {
                    if (NearestNeighbors[j, 1] > Distance)
                    {
                        for (int l = K - 1; l > j; l--)
                        {
                            NearestNeighbors[l, 0] = NearestNeighbors[l - 1, 0];
                            NearestNeighbors[l, 1] = NearestNeighbors[l - 1, 1];
                        }
                        NearestNeighbors[j, 0] = i;
                        NearestNeighbors[j, 1] = Distance;
                        break;
                    }
                }
            }
            for (int i = 0; i < K; i++)
            {
                prediction += Y[(int)NearestNeighbors[i, 0]];
            }
            prediction /= K;
            return prediction;
        }
        double k_NN(double[,] SortedData, double[,] Data, int NumberOfColumn, double[] Y, int VectorNumber) // used when column from Data is needed
        {
            double prediction = 0; // predicted value of Y[VectorNumber]
            double[,] NearestNeighbors = new double[K, 2]; // array contains numbers of nearest neigbors of SortedData[VectorNumber,]
            // filling NearestNeigbors with  first k vectors from SortedData
            for (int i = 0; i < K; i++)
            {
                NearestNeighbors[i, 0] = (double)i;
                NearestNeighbors[i, 1] = EuclideanDistance(SortedData, Data[VectorNumber, NumberOfColumn], Data[i, NumberOfColumn], VectorNumber, i);

            }

            // Sorting NearestNeighbors ([0] - nearest, [k] - farest)
            double p;
            double Distance;
            for (int i = 0; i < K - 1; i++)
            {
                for (int j = 0; j < K - 1; j++)
                {
                    if (NearestNeighbors[j, 1] > NearestNeighbors[j + 1, 1])
                    {
                        p = NearestNeighbors[j, 0];
                        Distance = NearestNeighbors[j, 1];
                        NearestNeighbors[j, 0] = NearestNeighbors[j + 1, 0];
                        NearestNeighbors[j, 1] = NearestNeighbors[j + 1, 1];
                        NearestNeighbors[j + 1, 0] = p;
                        NearestNeighbors[j + 1, 1] = Distance;
                    }
                }

            }
            // Finding nearest neighbors of SortedData[VectorNumber,]
            for (int i = K; i < TrainingLength; i++)
            {
                Distance = EuclideanDistance(SortedData, Data[VectorNumber, NumberOfColumn], Data[i, NumberOfColumn], VectorNumber, i);
                for (int j = 0; j < K; j++)
                {
                    if (NearestNeighbors[j, 1] > Distance)
                    {
                        for (int l = K - 1; l > j; l--)
                        {
                            NearestNeighbors[l, 0] = NearestNeighbors[l - 1, 0];
                            NearestNeighbors[l, 1] = NearestNeighbors[l - 1, 1];
                        }
                        NearestNeighbors[j, 0] = i;
                        NearestNeighbors[j, 1] = Distance;
                        break;
                    }
                }
            }
            for (int i = 0; i < K; i++)
            {
                prediction += Y[(int)NearestNeighbors[i, 0]];
            }
            prediction /= K;
            return prediction;
        }
        double MeanSquaredError(double[,] SortedData, double[,] Data, int NumberOfColumn, double[] Y) // Calculating mean squared error
        {
            double MSE = 0;
            for (int i = (int)TrainingLength; i < Y.Length; i++) // We starts from beginning of Test
            {
                double kNN = k_NN(SortedData, Data, NumberOfColumn, Y, i);
                //Console.WriteLine(kNN);
                MSE += Math.Pow((Y[i] - kNN), 2);
                //Console.WriteLine(MSE);
            }
            MSE = Math.Sqrt(MSE);
            MSE /= TestLength;
            return MSE;
        }
        public double MeanSquaredError(double[,] SortedData, double[] Y) // Calculating mean squared error for one double[,] array only
        {
            double MSE = 0;
            for (int i = (int)TrainingLength; i < Y.Length; i++) // We starts from beginning of Test
            {
                MSE += Math.Pow((Y[i] - k_NN(SortedData, Y, i)), 2);
            }
            MSE = Math.Sqrt(MSE);
            MSE /= TestLength;
            return MSE;
        }
        double Correlation(double[,] SortedData, double[,] Data, int NumberOfColumn) // Calculates the infuence
        {
            return 0;
        }
        double[,] AddColumn(double[,] SortedData, double[,] Data, int NumberOfColumn) // adds column "NumberOfColumn" from Data to SortedData
        {
            double[,] NewSortedData = new double[SortedData.GetLength(0), SortedData.GetLength(1) + 1];
            for (int i = 0; i < SortedData.GetLength(0); i++)
            {
                for (int j = 0; j < SortedData.GetLength(1); j++)
                {
                    NewSortedData[i, j] = SortedData[i, j];
                }
            }
            int k = NewSortedData.GetLength(1) - 1; // counter contains number of new column
            for (int i = 0; i < Data.GetLength(0); i++)
            {
                NewSortedData[i, k] = Data[i, NumberOfColumn];
            }
            return NewSortedData;

        }
        int[] RemoveColumn(int[] ArrayOfColumns, int NumberOfColumn) // removes column "NumberOfColumn" from ArrayOfColumns
        {
            int[] NewArray = new int[ArrayOfColumns.Length - 1];
            for (int i = 0; i < NumberOfColumn; i++) // from column 0 to column "NumberOfColumn"-1
            {
                NewArray[i] = ArrayOfColumns[i];
            }
            for (int i = NumberOfColumn + 1; i < ArrayOfColumns.Length; i++) // from column "NumberOfColumn"+1 to column ArrayOfColumns.Length-1
            {
                NewArray[i - 1] = ArrayOfColumns[i];
            }
            return NewArray;
        }
        double[,] RemoveColumn(double[,] Data, int NumberOfColumn) // removes column "NumberOfColumn" from Data
        {
            double[,] NewData = new double[Data.GetLength(0), Data.GetLength(1) - 1];
            for (int i = 0; i < NumberOfColumn; i++) // from column 0 to column "NumberOfColumn"-1
            {
                for (int k = 0; k < Data.GetLength(0); k++)
                {
                    NewData[k, i] = Data[k, i];
                }
            }
            for (int i = NumberOfColumn + 1; i < Data.GetLength(1); i++) // from column "NumberOfColumn"+1 to column Data.GetLength(1)-1
            {
                for (int k = 0; k < Data.GetLength(0); k++)
                {
                    NewData[k, i - 1] = Data[k, i];
                }
            }
            return NewData;
        }

        public double[,] ForwardSelection(double[,] Data, string TextFileName)
        {
            int ProgressVariable = 0;
            ChangeProgress((int)((ProgressVariable++ / Data.GetLength(1)) * 100));
            StreamWriter SW = new StreamWriter(TextFileName);
            int[] ArrayOfColumns = new int[Data.GetLength(1) - 1]; // we need array containg indexes of columns that we will use
            //int columnY = 4; 
            //this.K = 3;
            for (int i = 0; i < columnY; i++)
            {
                ArrayOfColumns[i] = i;
            }
            for (int i = columnY + 1; i < Data.GetLength(1); i++)
            {
                ArrayOfColumns[i - 1] = i;
            }
            double[] Y = new double[Data.GetLength(0)];
            for (int i = 0; i < Data.GetLength(0); i++)
            {
                Y[i] = Data[i, columnY];
            }
            int[] IndexesOfSortedData = new int[Data.GetLength(1)]; // contains indexes of columns from Data in SortedData
            int IOSDCounter = 0; // counter - how many indexes is already in IndexesOfSortedData
            // *** *** *** *** *** *** *** *** *** *** *** *** ***
            TrainingLength = (Data.GetLength(0) * 2) / 3;
            TestLength = Data.GetLength(0) - TrainingLength;
            double[,] SortedData = new double[Data.GetLength(0), 0]; // new empty array that will contain data sorted by evaluation
            int n = ArrayOfColumns.Length;
            bool change;  // true - when CalculatedEvaluation > CurrentEvaluation
            double CalculatedCorrelation; // will change with every use of Evaluation() method
            double CurrentCorrelation = 0;  // consists max evaluation
            int NumberOfColumn = 0; // number of column, which be either added or removed
            change = false;
            // Searching for best feature (using Correlation())
            for (int i = 0; i < n; i++) // from column 0 to column n-1
            {
                CalculatedCorrelation = MeanSquaredError(SortedData, Data, ArrayOfColumns[i], Y);
                SW.WriteLine("F" + i + "\t" + CalculatedCorrelation);
                if (Math.Abs(CalculatedCorrelation) > CurrentCorrelation)
                {
                    CurrentCorrelation = CalculatedCorrelation;
                    NumberOfColumn = i;
                    change = true;
                }
            }
            SW.WriteLine("\nSelected F" + NumberOfColumn + "\n");
            if (change)
            {
                SortedData = AddColumn(SortedData, Data, ArrayOfColumns[NumberOfColumn]);
                IndexesOfSortedData[IOSDCounter] = ArrayOfColumns[NumberOfColumn];
                IOSDCounter++;
                ArrayOfColumns = RemoveColumn(ArrayOfColumns, NumberOfColumn);
                n = ArrayOfColumns.Length; // ArrayOfColumns.Length got smaller so we should change counter n
            }
            ChangeProgress((int)((ProgressVariable++ / Data.GetLength(1)) * 100));
            // Calculating MeanSquaredError
            double OldMSE, NewMSE = 0;
            //NewMSE = MeanSquaredError(SortedData, Y);         unnecessary
            change = false;
            // filling SortedData (using MeanSquaredError())
            do
            {
                OldMSE = NewMSE;
                NewMSE = 0;
                double[] MSEArray = new double[n];
                // Using Parallel.For
                Parallel.For(0, n, i =>
                {
                    MSEArray[i] = MeanSquaredError(SortedData, Data, ArrayOfColumns[i], Y);
                    for (int j = 0; j < IOSDCounter; j++)
                    {
                        SW.Write("F" + IndexesOfSortedData[j]);
                    }
                    SW.WriteLine("F" + ArrayOfColumns[i] + "\t" + MSEArray[i]);
                }
                );
                NewMSE = MSEArray[0];
                NumberOfColumn = 0;
                for (int i = 1; i < n - 1; i++)
                {
                    if (NewMSE < MSEArray[i])
                    {
                        NewMSE = MSEArray[i];
                        NumberOfColumn = i;
                    }
                }
                // Using normal for
                //for (int i = 0; i < n; i++)
                //{
                //    NewMSE2 = MeanSquaredError(SortedData,Data,ArrayOfColumns[i],Y);
                //    for (int j = 0; j < IOSDCounter; j++)
                //    {
                //        SW.Write("F" + IndexesOfSortedData[j]);
                //    }
                //    SW.WriteLine("F" + ArrayOfColumns[i] + "\t" + NewMSE2);

                //    if (NewMSE2 > NewMSE)
                //    {
                //        NewMSE = NewMSE2;
                //        NumberOfColumn = i;
                //    }
                //}
                change = OldMSE < NewMSE;
                if (change)
                {
                    SortedData = AddColumn(SortedData, Data, ArrayOfColumns[NumberOfColumn]);
                    IndexesOfSortedData[IOSDCounter] = ArrayOfColumns[NumberOfColumn];
                    IOSDCounter++;
                    ArrayOfColumns = RemoveColumn(ArrayOfColumns, NumberOfColumn);
                    n = ArrayOfColumns.Length; // ArrayOfColumns.Length got smaller so we should change counter n
                    SW.WriteLine("\nSelected F" + ArrayOfColumns[NumberOfColumn] + "\n");
                    ChangeProgress((int)((ProgressVariable++ / Data.GetLength(1)) * 100));

                }
                else
                {
                    SW.WriteLine("There is no improvement");
                    ChangeProgress(100);
                }
            } while (change && n > 1); // stop where there is no change or Data had expired
            SW.Close();
            return SortedData;
        }


        public void ChangeProgress(int progress)
        {

        }
        public void ChangeStatus(string txt)
        {

        }
    }
}
